const requestPrevTxInfo = (reqTx, requestType, requestIndex, dataLen, dataOffset) => {
  const i = +requestIndex
  if (requestType === 'TXINPUT') {
    return { inputs: [reqTx.inputs[i]] }
  }
  if (requestType === 'TXOUTPUT') {
    return { bin_outputs: [reqTx.bin_outputs[i]] }
  }
  if (requestType === 'TXEXTRADATA') {
    if (dataLen == null) {
      throw new Error('Missing extra_data_len')
    }
    const dataLenN = +dataLen

    if (dataOffset == null) {
      throw new Error('Missing extra_data_offset')
    }
    const dataOffsetN = +dataOffset

    if (reqTx.extra_data == null) {
      throw new Error('No extra data for transaction ' + reqTx.hash)
    }

    const data = reqTx.extra_data
    const substring = data.substring(dataOffsetN * 2, (dataOffsetN + dataLenN) * 2)
    return { extra_data: substring }
  }
  if (requestType === 'TXMETA') {
    const outputCount = reqTx.bin_outputs.length
    const data = reqTx.extra_data
    const meta = {
      version: reqTx.version,
      lock_time: reqTx.lock_time,
      inputs_cnt: reqTx.inputs.length,
      outputs_cnt: outputCount,
      timestamp: reqTx.timestamp,
      version_group_id: reqTx.version_group_id
    }

    if (typeof data === 'string' && data.length !== 0) {
      return {
        ...meta,
        extra_data_len: data.length / 2
      }
    }

    return meta
  }
  throw new Error(`Unknown request type: ${requestType}`)
}

const requestSignedTxInfo = (inputs, outputs, requestType, requestIndex) => {
  const i = +requestIndex
  if (requestType === 'TXINPUT') {
    return { inputs: [inputs[i]] }
  }
  if (requestType === 'TXOUTPUT') {
    return { outputs: [outputs[i]] }
  }
  if (requestType === 'TXMETA') {
    throw new Error('Cannot read TXMETA from signed transaction')
  }
  if (requestType === 'TXEXTRADATA') {
    throw new Error('Cannot read TXEXTRADATA from signed transaction')
  }
  throw new Error(`Unknown request type: ${requestType}`)
}

const requestTxInfo = (m, index, inputs, outputs) => {
  const md = m.details
  const hash = md.tx_hash
  if (hash) {
    const hexHash = Buffer.from(hash, 'base64').toString('hex')
    const reqTx = index[hexHash.toLowerCase()]
    if (!reqTx) {
      throw new Error(`Requested unknown tx: ${hash}`)
    }
    return requestPrevTxInfo(
      reqTx,
      m.request_type,
      md.request_index,
      md.extra_data_len,
      md.extra_data_offset
    )
  } else {
    return requestSignedTxInfo(inputs, outputs, m.request_type, md.request_index)
  }
}

const saveTxSignatures = (ms, serializedTx, signatures) => {
  if (ms) {
    const _signatureIndex = ms.signature_index
    const _signature = ms.signature
    const _serializedTx = ms.serialized_tx
    if (_serializedTx != null) {
      serializedTx.serialized += _serializedTx
    }
    if (_signatureIndex != null) {
      if (_signature == null) {
        throw new Error('Unexpected null in trezor:TxRequestSerialized signature.')
      }
      signatures[_signatureIndex] = _signature
    }
  }
}

const processTxRequest = async (m, serializedTx, signatures, index, inputs, outputs) => {
  saveTxSignatures(m.serialized, serializedTx, signatures)

  if (m.request_type === 'TXFINISHED') {
    return Promise.resolve({
      signatures: signatures,
      serializedTx: serializedTx.serialized
    })
  }

  const resTx = requestTxInfo(m, index, inputs, outputs)

  const response = await webusb.cmd('TxAck', { tx: resTx })
  return await processTxRequest(
    response.data,
    serializedTx,
    signatures,
    index,
    inputs,
    outputs
  )
}

webusb.signTransaction = async function({
  coin_name,
  inputs,
  outputs,
  utxo,
  version,
  lock_time
}) {
  const index = {}
  utxo.forEach(tx => {
    index[tx.hash.toLowerCase()] = tx
  })
  const signatures = []
  const serializedTx = { serialized: '' }

  const response = await this.cmd('SignTx', {
    coin_name,
    inputs_count: inputs.length,
    outputs_count: outputs.length,
    version,
    lock_time
  })
  const signed = await processTxRequest(
    response.data,
    serializedTx,
    signatures,
    index,
    inputs,
    outputs
  )

  return signed
}